/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.maven.server.execution;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.Future;
import java.util.function.Consumer;

/**
 * Asynchronously non blocking read process output.
 * Class use external executor to run read thread
 *
 * @author Evgen Vidolob
 */
public class OutputReader {
    private static final Logger LOG = LoggerFactory.getLogger(OutputReader.class);

    private static final int SLEEP_WHEN_WAS_ACTIVE = 1;
    private static final int SLEEP_WHEN_IDLE       = 5;

    private final Executor         executor;
    private final Consumer<String> textConsumer;
    private final Reader           reader;

    private final char[]        buffer      = new char[8192];
    private final StringBuilder lineBuilder = new StringBuilder();

    private Future<?> readingFuture;

    private volatile boolean isStopped;

    public OutputReader(Reader reader, Executor executor, Consumer<String> textConsumer) {
        this.executor = executor;
        this.textConsumer = textConsumer;
        this.reader = reader;
    }

    /**
     * Start reading thread
     */
    public void start() {
        if (readingFuture == null) {
            readingFuture = executor.execute(() -> doStart());
        }
    }

    public void stop() {
        isStopped = true;
    }

    public void waitFor() throws InterruptedException {
        try {
            readingFuture.get();
        } catch (java.util.concurrent.ExecutionException e) {
            LOG.error(e.getMessage(), e);
        }
    }


    private void doStart() {
        try {
            while (true) {
                boolean active = readIfAvailable();
                if (isStopped) {
                    break;
                }

                try {
                    Thread.sleep(getTimeToSleep(active));
                } catch (InterruptedException ignored) {
                }
            }
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        } finally {
            close();
        }
    }


    private void close() {
        try {
            reader.close();
        } catch (IOException e) {
            LOG.error("Can't close stream", e);
        }
    }

    private int getTimeToSleep(boolean active) {
        return active ? SLEEP_WHEN_WAS_ACTIVE : SLEEP_WHEN_IDLE;
    }

    private boolean readIfAvailable() throws IOException {
        boolean read = false;
        int charCount;
        while (reader.ready() && (charCount = reader.read(buffer)) > 1) {
            read = true;
            processRead(lineBuilder, buffer, charCount);
        }

        if (lineBuilder.length() > 0) {
            consumeLine(lineBuilder);
        }
        return read;
    }

    private void consumeLine(StringBuilder lineBuilder) {
        textConsumer.accept(lineBuilder.toString());
        lineBuilder.setLength(0);
    }

    private void processRead(StringBuilder lineBuilder, char[] buffer, int charCount) {
        for (int i = 0; i < charCount; i++) {
            char c = buffer[i];
            lineBuilder.append(c);
            if (c == '\n') {
                consumeLine(lineBuilder);
            }
        }
    }

}
